A quick intro to the intro to R Lesson Series


This ‘Intro to R Lesson Series’ is brought to you by the Centre for the Analysis of Genome Evolution & Function’s (CAGEF) bioinformatics training initiative. This course was developed based on feedback on the needs and interests of the Department of Cell & Systems Biology and the Department of Ecology and Evolutionary Biology.

This lesson is the third in a 6-part series. The idea is that at the end of the series, you will be able to import and manipulate your data, make exploratory plots, perform some basic statistical tests, test a regression model, and make some even prettier plots and documents to share your results.


How do we get there? Today we are going to be learning how to make all sorts of plots - from simple data exploration to interactive plots.The next lesson will be data cleaning and string manipulation; this is really the battleground of coding - getting your data into the format where you can analyse it. Then we will learn how to do t-tests and perform regression and modeling in R. And lastly, we will learn to write some functions, which really can save you time and help scale up your analyses.


The structure of the class is a code-along style. It is hands on. The lecture AND code we are going through are available on GitHub for download at https://github.com/eacton/CAGEF (Note: repo is private until approved), so you can spend the time coding and not taking notes. As we go along, there will be some challenge questions and multiple choice questions on Socrative. At the end of the class if you could please fill out a post-lesson survey (https://www.surveymonkey.com/r/VNQZ3KS), it will help me further develop this course and would be greatly appreciated.


Packages Used in This Lesson

The following packages are used in this lesson:

tidyverse (ggplot2, tidyr, dplyr)
(twitteR)* tidytext
viridis

*Used to generate the tweet tables used in this lesson. It is not necessary for you to install this - you can work from the tables. If you want to create these files - the code is here …………….(insert link).

Please install and load these packages for the lesson. In this document I will load each package separately, but I will not be reminding you to install the package. Remember: these packages may be from CRAN OR Bioconductor.


Highlighting

grey background - a package or function or code
italics - an important term or concept
bold - heading or ‘grammar of graphics’ term


Objective: At the end of this session you will be able to use regular expressions to ‘clean’ your data. You will also learn R markdown and be able to render your R code into slides, a pdf, html, a word document, or a notebook.


Data Cleaning or Data Munging or Data Wrangling

Why do we need to do this?

‘Raw’ data is seldom (never) in a useable format. Data in tutorials or demos has already been meticulously filtered, transformed and readied for that specific analysis. How many people have done a tutorial only to find they can’t get their own data in the @*%($! format to use the tool they have just spend an hour learning about???

Data cleaning requires us to:

Some definitions might take this a bit farther and include normalizing data and removing outliers, but I consider data cleaning at least, getting data into a format where we can start actively doing ‘the maths or the graphs’ whether it be statistical calculations, normalization or exploratory plots. We have learned how to transform data into a tidy format in Lesson 2, but the prelude to transforming data is doing the grunt work mentioned above. So let’s get to it!


Intro to regular expressions

Regular expressions

“A God-awful and powerful language for expressing patterns to match in text or for search-and-replace. Frequently described as ‘write only’, because regular expressions are easier to write than to read/understand. And they are not particularly easy to write.” - Jenny Bryan


So why does regex get so much flak?

Scary example: how to get an email in different programming languages (http://emailregex.com/). Whatever.


What does the language look like?

Matching by position

Quantifiers

Character classes

Operators

Escape characters

Data Cleaning with Base R (AKA What is Elon Musk up to anyways?)

Let’s perform some basic data cleaning tasks with an actual (fun?) messy data set. I have scraped Elon Musk’s latest tweets from Twitter. The code to do this is in the Lesson 4 markdown file if you are curious and/or want to creep someone on Twitter.

Let’s read in the set of tweets, take a look at the structure of the data, and use ‘tidyverse’ to order the data by the most popular (favorited) tweets. Let’s check out the top 5 favorite tweets.

elon_tweets_df$text[1:5]
[1] 0 to 100 km/h in 1.9 sec https://t.co/xTOTDGuwQj                                                                                            
[2] Apparently, some customs agencies are saying they won’t allow shipment of anything called a “Flamethrower”. To solv… https://t.co/OCtjvdXo95
[3] The rumor that I’m secretly creating a zombie apocalypse to generate demand for flamethrowers is completely false                           
[4] Nuclear alien UFO from North Korea https://t.co/GUIHpKkkp5                                                                                  
[5] Ok, who leaked my selfie!? https://t.co/fYKXbix8jw                                                                                          
347 Levels: @_iitzBrendon Elon University is a real place ...

Our end goal is going to be to look at the top 50 words in Elon Musk’s tweets. I emphasize words, because I don’t want urls, or hastags, or other tags. I also don’t want punctuation or spaces. I want to extract just the words from tweets. First, I want to get remove the tags from the beginning of words. Because regex expressions can get ‘ugly’, I am going to save my regex expression into an object - that way I am typing something like tags repeatedly instead of #|@\\w+ repeatedly.

What this expression says is that I want to find matches for a hastag OR an asperand followed by at least one word character.

grep(tags, elon_tweets_df$text)
  [1]  27  34  45  49  56  57  58  70  73  74  81  82  87  89  93  98 103 117 118 119 122 123 129 131 132 134 135 142 147 148 151
 [32] 154 158 159 160 164 166 169 171 172 173 174 175 177 179 181 182 183 184 185 187 188 189 192 193 194 196 197 198 201 202 203
 [63] 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234
 [94] 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265
[125] 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
[156] 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327
[187] 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348

Okay. grep returns the index of the match. We have a number of entries that include tags. However, we are immediately getting an error for lines 10, 118, 156, 219 and 224 being ‘invalid’. This is a good opportunity for trouble-shooting. What would you do next? Let’s check out those lines.

elon_tweets_df[c(10,188,156,219,224), "text"]
[1] I just realized there is a jazz hands emoji \xed\xa0\xbe\xed\xb4\x97                              
[2] @andrestaltz Will be simpler than IPv6 and have tiny packet overhead. Definitely peer-to-peer.    
[3] Great for roasting nuts \xed\xa0\xbd\xed\xb4\xa5 \xed\xa0\xbe\xed\xb5\x9c https://t.co/MGmkSJhIyx 
[4] @jeremyburge Literally, if you type “jazz hands”, iOS shows this emoji \xed\xa0\xbe\xed\xb4\x97   
[5] @omaruddin @stevewoz @Tesla @businessinsider Woz is a lovable, fuzzy bear \xed\xa0\xbd\xed\xb0\xbb
347 Levels: @_iitzBrendon Elon University is a real place ...

I think these are emojis. What would you do next? Since these are not words, they should be removed. Since we are getting an invalid locale call, let’s check out our locale.

Sys.getlocale()

Looks like we are set in Canada. Let’s turn off locale-specific settings.

Sys.setlocale('LC_ALL','C')

If we run our tags code again, we now don’t get an error and line 188, which we can see has a tag, is included.

grep(tags, elon_tweets_df$text)

In order to really check that each line indeed has a tag, we can set value = TRUE in grep, and instead we will be returned the text that is at that position. Visually inspecting, it looks like each tweet has a tag.

grep(tags, elon_tweets_df$text, value = TRUE)

This reminds me that we should also remove emojis.

left off here

grep(emojis, elon_tweets_df$text)
[1]  10 118 156 219 224 292

Data Cleaning with stringr/stringi (AKA What is Trump up to anyways?)

library(tidyverse)

elon_tweets_df <- read.delim("data/elon_tweets_df.txt", sep = "\t")
str(elon_tweets_df)


elon_tweets_df <- elon_tweets_df %>% arrange(desc(as.numeric(retweetCount)))
elon_tweets_df <- elon_tweets_df %>% arrange(desc(favoriteCount))
elon_tweets_df$text[1:5]

nye_tweets <- userTimeline("BillNye", n = 3200)
nye_tweets_df <- tbl_df(map_df(nye_tweets, as.data.frame))

write.table(nye_tweets_df, "data/nye_tweets_df.txt", sep = "\t")
nye_tweets_df <- read.delim("data/nye_tweets_df.txt", sep = "\t")

nye_tweets_df <- nye_tweets_df %>% arrange(desc(as.numeric(retweetCount)))
nye_tweets_df <- nye_tweets_df %>% arrange(desc(favoriteCount))
nye_tweets_df$text[1:5]

jt_tweets <- userTimeline("JustinTrudeau", n = 3200)
jt_tweets_df <- tbl_df(map_df(jt_tweets, as.data.frame))

write.table(jt_tweets_df, "data/jt_tweets_df.txt", sep = "\t")
jt_tweets_df <- read.delim("data/jt_tweets_df.txt", sep = "\t")

jt_tweets_df <- jt_tweets_df %>% arrange(desc(as.numeric(retweetCount)))
jt_tweets_df <- jt_tweets_df %>% arrange(desc(favoriteCount))
jt_tweets_df$text[1:5]

colbert_tweets <- userTimeline("StephenAtHome", n = 3200)
colbert_tweets_df <- tbl_df(map_df(colbert_tweets, as.data.frame))

write.table(colbert_tweets_df, "data/colbert_tweets_df.txt", sep = "\t")
colbert_tweets_df <- read.delim("data/colbert_tweets_df.txt", sep = "\t")

colbert_tweets_df <- colbert_tweets_df %>% arrange(desc(as.numeric(retweetCount)))
colbert_tweets_df <- colbert_tweets_df %>% arrange(desc(favoriteCount))
colbert_tweets_df$text[1:5]
colbert_tweets_df$text[5:10]

daily_tweets <- userTimeline("TheDailyShow", n = 3200)
daily_tweets_df <- tbl_df(map_df(daily_tweets, as.data.frame))

write.table(daily_tweets_df, "data/daily_tweets_df.txt", sep = "\t")
daily_tweets_df <- read.delim("data/daily_tweets_df.txt", sep = "\t")

daily_tweets_df <- daily_tweets_df %>% mutate(retweetCount = as.numeric(retweetCount)) %>% arrange(desc(retweetCount))
daily_tweets_df <- daily_tweets_df %>% arrange(desc(favoriteCount))
daily_tweets_df$text[1:5]
daily_tweets_df$text[8:12]

trump_tweets <- userTimeline("realDonaldTrump", n = 3200)
trump_tweets_df <- tbl_df(map_df(trump_tweets, as.data.frame))

write.table(trump_tweets_df, "data/trump_tweets_df.txt", sep = "\t")
trump_tweets_df <- read.delim("data/trump_tweets_df.txt", sep = "\t")

trump_tweets_df <- trump_tweets_df %>% mutate(retweetCount = as.numeric(retweetCount)) %>% arrange(desc(retweetCount))
trump_tweets_df <- trump_tweets_df %>% arrange(desc(favoriteCount))
trump_tweets_df$text[1:5]
trump_tweets_df$text[8:12]

-rounding data - we don’t need it to the umpteenth decimal -look for lowest and highest values - do these make sense? (standard deviation?) -called a range check, spell check, regex -document what you are doing -no all data sets are 100% clean -also the paste functions

-the basics running through a fun example - do a chosen one, see what other problems come up -rmarkdown, syntax, etc -make a pdf of cleaning the WellcomeTrust dataset (give a brief outline of what needs to be done)

kieran had a really good lesson using twitter http://stat545.com/block027_regular-expressions.html

jenny’s lesson good content but kind of boring http://stat545.com/block028_character-data.html

–good content but sick of trump

getting tweets and basic stats http://varianceexplained.org/r/trump-tweets/

remember regex testers https://regex101.com/ https://regexr.com/

Something normal to do in twitter would be to find hashtags.

tags <- "#\\w+"

#elon apparently doesn't use hashtags

#let me guess that it's going to be '#sciencerules'
grep(tags, nye_tweets_df$text)

grep(tags, nye_tweets_df$text, value = TRUE)

grepl(tags, nye_tweets_df$text)


#to return the data frame 
nye_tags <- nye_tweets_df %>% filter(grepl(tags, nye_tweets_df$text))
jt_tags <- jt_tweets_df %>% filter(grepl(tags, jt_tweets_df$text))

Get rid of urls.

url <- "http[s]?://[[:alnum:].\\/]+"

#replace with nothing

nye_tweets_df$text <- gsub(pattern = "http[s]?://[[:alnum:].\\/]+", replacement = "", nye_tweets_df$text)

Get rid of trailing spaces.

trail <- "[ ]+$"

nye_tweets_df$text <- gsub(pattern = "[ ]+$", replacement = "", nye_tweets_df$text)

Let’s break the texts down into individual words, so we can see what the most common words used are.

library(stringr)

str_split(nye_tweets_df$text, "\\s", simplify = TRUE)[1,]

#anything followed by anything and word boundary
str_extract_all(nye_tweets_df$text, "(@|#|\\b|[[:alnum:]]+['])[[:alnum:]]+")
#cool
save <- str_extract_all(nye_tweets_df$text, "[#|@|[:alnum:]]+([^\\s][[:alnum:]]+)?", simplify = TRUE)
#gather, get rid of empty strings
test <- gather(as.data.frame(save)) %>% filter(value != "")

test$value <- tolower(test$value)

test <- test %>% select(value) %>% count(factor(value)) %>% arrange(desc(n))

Wow. We have discovered people use prepositions and conjunctions. There is a list of ‘stop words’ that can be used to get rid of words that are unlikely to contain information for us as part of the tidytext package.

library(tidytext)
nope <- stop_words

nye_words <- anti_join(test, nope, by=c("factor(value)" = "word"))

This looks much better. An ‘amp’ is an ampersand (&). But otherwise we get ‘science’, ‘world’, ‘space’, ‘change’, ‘join’, ‘climate’, ‘launch’, vote’, ‘earth’ and other things we might expect from the science guy. I guess Jack and the Geniuses is a book series by Bill Nye.

(it, its and it is were all in the stop list - it’s should probably be in there). And it’s interesting to note these little variations because no matter how much you try to automate your analysis there is always going to be something from your new dataset that didn’t fit with your old dataset. This is why we need these data wrangling skills. Even though some packages may have been created to help us on our way, they can’t possibly cover every case.

What will we get from the Elon Musk?

elon_tweets_df$text <- gsub(pattern = "http[s]?://[[:alnum:].\\/]+", replacement = "", elon_tweets_df$text)

elon_tweets_df$text <- gsub(pattern = "[ ]+$", replacement = "", elon_tweets_df$text)


save <- str_extract_all(elon_tweets_df$text, "[#|@|[:alnum:]]+([^\\s][[:alnum:]]+)?", simplify = TRUE)
#gather, get rid of empty strings
test <- gather(as.data.frame(save)) %>% filter(value != "")

test$value <- tolower(test$value)

test <- test %>% select(value) %>% count(factor(value)) %>% arrange(desc(n))

elon_words <- anti_join(test, nope, by=c("factor(value)" = "word"))
anti_join(test, nope, by=c("factor(value)" = "word"))

‘boring’, ‘falcon’, ‘tesla’, ‘rocket’, ‘@spacex’, ‘company’, ‘love’, ‘launch’, ‘ai’, ‘cars’. ‘flamethrower’ and ‘mars’ are a bit further down.

nrc <- sentiments %>%
  filter(lexicon == "nrc") %>%
  dplyr::select(word, sentiment)

nrc
#abacus associated with trust...weird

by_source_sentiment <- trump_words %>%
  inner_join(nrc, by =c("factor(value)" = "word")) %>%
  count(sentiment) %>% 
  arrange(desc(nn))
  

head(by_source_sentiment)
daily_tweets_df$text <- gsub(pattern = "http[s]?://[[:alnum:].\\/]+", replacement = "", daily_tweets_df$text)

daily_tweets_df$text <- gsub(pattern = "[ ]+$", replacement = "", daily_tweets_df$text)


save <- str_extract_all(daily_tweets_df$text, "[#|@|[:alnum:]]+([^\\s][[:alnum:]]+)?", simplify = TRUE)
#gather, get rid of empty strings
test <- gather(as.data.frame(save)) %>% filter(value != "")

test$value <- tolower(test$value)

test <- test %>% select(value) %>% count(factor(value)) %>% arrange(desc(n))

daily_words <- anti_join(test, nope, by=c("factor(value)" = "word"))
daily_words <- anti_join(test, nope, by=c("factor(value)" = "word"))
trump_tweets_df$text <- gsub(pattern = "http[s]?://[[:alnum:].\\/]+", replacement = "", trump_tweets_df$text)

trump_tweets_df$text <- gsub(pattern = "[ ]+$", replacement = "", trump_tweets_df$text)


save <- str_extract_all(trump_tweets_df$text, "[#|@|[:alnum:]]+([^\\s][[:alnum:]]+)?", simplify = TRUE)
#gather, get rid of empty strings
test <- gather(as.data.frame(save), value = "word") %>% filter(word != "")

test$word <- tolower(test$word)

test <- test %>% select(word) %>% count(word) %>% arrange(desc(n))

trump_words <- anti_join(nope, nope, by= "word")

It looks like we need some more data cleaning. First, let’s get rid of everything with numbers.

trump_words %>% select(word) %>% str_remove("[0-9]+", simplify)

#removes numbers
str_remove_all(trump_words$word, "[0-9]*")
#still have punctuation before numbers
str_remove_all(trump_words$word, "[0-9].*")

It’s looking better. We have a single hashtag. “‘s" endings should be removed - could match other words, or if a contraction will be removed via stopwords list. There is also a ’u.s’ where we can get rid of the period. If anyone can find out how to remove the apostrophe and not the period, let me know.

str_remove_all(trump_words$word, "#$")


gsub("[[:punct:]]s$", "", trump_words$word)
#let's check to see what this will remove.
grep("[[:punct:]]s$", trump_words$word, value=TRUE)
#the only thing that isn't an 's is u.s, we don't want this removed and truncated to 'u', but we also don't want to just remove the period first because we want to retain that it means united states and not us. So, I actually couldn't find a regex punctuation solution to this. BONUS points if you do. Instead, we are going to REPLACE "u.s" with "usa"

trump_words$word <- str_replace(trump_words$word, "u.s", "usa")

#check
grep("[[:punct:]]s$", trump_words$word, value=TRUE)

trump_words$word <- str_remove_all(trump_words$word, "[[:punct:]]s$")
trump_words$word <- str_remove_all(trump_words$word, "#$")

#check
grep("[[:punct:]]s$", trump_words$word, value=TRUE)


#once we know we've got it right we can filter the data frame
trump_words <- trump_words %>% mutate(word = str_remove_all(trump_words$word, "[0-9].*")) %>% filter(word != "")

We need to add to our ‘nope’ list words unrelated to content but html jargon, or things like ‘na’ and ‘false’.

add_stop <- data.frame(word = c("na", "false", "href", "rel", "nofollow", "true", "amp", "twitter", "iphone"), 
                       lexicon = "custom")
#I just should point out that true or false 'could' be a word rather than a logical, I could filter out FALSE and TRUE before changing everything to lowercase and this would reduce the likelyhood of missing 'words'
nope <- bind_rows(nope, add_stop)
trump_words <- anti_join(test, nope, by= "word")

Both Nye and Musk positive. Article does p-value and such. Probably just should do more regex. http://varianceexplained.org/r/trump-tweets/

favourited probably better than retweeted if using one

Challenge question: Pick one of the other tweet data sets. Clean it. Remove all of the stop words. Make a bar plot of the top 20 words and their counts.

library("wordcloud")
library("viridis")

trump_words %>%
    top_n(50) %>%
    with(wordcloud(word, n, ordered.colors = TRUE, colors = viridis(61), use.r.layout = TRUE))

I looked for a dataset for data cleaning and found it in a blog titled “Biologists: this is why bioinformaticians hate you…”. The main and common issue with this dataset is that when data entry was done there was no structured vocabulary - meaning that people could type in whatever they wanted instead of using dropdown menus with limited options, or giving an error if something is formatted incorrectly, or stipulating some rules (ie. must be all lowercase, uppercase, no numbers, spacing, etc.). I must admit I have been guilty of messing with people who have made databases without rules. For example, in giving the emergency contact there was a line to input ‘Relationship’, which could easily have been a dropdown menu ‘parent, partner, friend, other’, but instead I was allowed to write in a free text line ‘lifelong kindred spirit, soulmate and doggy-daddy’. I don’t think anyone here was trying to be a nuisance, this is just a consequence of poor data collection. There is a README file to go with this spreadsheet if you have questions about the data fields.

http://www.opiniomics.org/biologists-this-is-why-bioinformaticians-hate-you/
https://figshare.com/articles/Wellcome_Trust_APC_spend_2012_13_data_file/963054

What I want to know is: 1. List 5 problems with this data set. 1. Which publisher is the most expensive to publish with? 1. Which journal is the most expensive to publish with? Is this by the same publisher?
1. Convert sterling to CAD. What is the median cost of publishing with Elsevier in CAD?

The blogger’s opinion of cleaning this dataset:

‘I now have no hair left; I’ve torn it all out. My teeth are just stumps from excessive gnashing. My faith in humanity has been destroyed!’

Don’t get to this point. The dataset doesn’t need to be perfect. Just do what you gotta do to answer these questions.

Note to self: This may be too tough - see how long it takes to do.

Approximate time: 2 hours per lesson

Each lesson will have:


R markdown and knitr

Challenge:

Take the original gapminder dataset and covert it to the ‘clean’ dataset found in the gapminder package / find some horrible dataset to clean. Present in a knitr table, explaining some of your data cleaning challenges in rmarkdown. Knit the document to a pdf.

Resources:
http://stat545.com/block022_regular-expression.html http://stat545.com/block027_regular-expressions.html http://stat545.com/block028_character-data.html
http://r4ds.had.co.nz/strings.html http://www.gastonsanchez.com/Handling_and_Processing_Strings_in_R.pdf
http://varianceexplained.org/r/trump-tweets/

Post-Lesson Assessment


Questions

Notes


LS0tCnRpdGxlOiAiTGVzc29uIDQgLSBEYXRhIENsZWFuaW5nL1N0b3AgV3Jlc3RsaW5nIHdpdGggUmVndWxhciBFeHByZXNzaW9ucyIKb3V0cHV0OiAKICBodG1sX2RvY3VtZW50OgogICAgICAgICAga2VlcF9tZDogeWVzCiAgICAgICAgICB0b2M6IFRSVUUKICAgICAgICAgIHRvY19kZXB0aDogMwogIGh0bWxfbm90ZWJvb2s6CiAgICAgICAgICB0b2M6IFRSVUUKICAgICAgICAgIHRvY19kZXB0aDogMwotLS0KKioqCiFbXShpbWcvYmlnLWRhdGEtYm9yYXQucG5nKXt3aWR0aD00MDBweH0gCgo8L2JyPgoKIyNBIHF1aWNrIGludHJvIHRvIHRoZSBpbnRybyB0byBSIExlc3NvbiBTZXJpZXMKCjwvYnI+CgpUaGlzICdJbnRybyB0byBSIExlc3NvbiBTZXJpZXMnIGlzIGJyb3VnaHQgdG8geW91IGJ5IHRoZSBDZW50cmUgZm9yIHRoZSBBbmFseXNpcyBvZiBHZW5vbWUgRXZvbHV0aW9uICYgRnVuY3Rpb24ncyAoQ0FHRUYpIGJpb2luZm9ybWF0aWNzIHRyYWluaW5nIGluaXRpYXRpdmUuIFRoaXMgY291cnNlIHdhcyBkZXZlbG9wZWQgYmFzZWQgb24gZmVlZGJhY2sgb24gdGhlIG5lZWRzIGFuZCBpbnRlcmVzdHMgb2YgdGhlIERlcGFydG1lbnQgb2YgQ2VsbCAmIFN5c3RlbXMgQmlvbG9neSBhbmQgdGhlIERlcGFydG1lbnQgb2YgRWNvbG9neSBhbmQgRXZvbHV0aW9uYXJ5IEJpb2xvZ3kuIAoKCgpUaGlzIGxlc3NvbiBpcyB0aGUgdGhpcmQgaW4gYSA2LXBhcnQgc2VyaWVzLiBUaGUgaWRlYSBpcyB0aGF0IGF0IHRoZSBlbmQgb2YgdGhlIHNlcmllcywgeW91IHdpbGwgYmUgYWJsZSB0byBpbXBvcnQgYW5kIG1hbmlwdWxhdGUgeW91ciBkYXRhLCBtYWtlIGV4cGxvcmF0b3J5IHBsb3RzLCBwZXJmb3JtIHNvbWUgYmFzaWMgc3RhdGlzdGljYWwgdGVzdHMsIHRlc3QgYSByZWdyZXNzaW9uIG1vZGVsLCBhbmQgbWFrZSBzb21lIGV2ZW4gcHJldHRpZXIgcGxvdHMgYW5kIGRvY3VtZW50cyB0byBzaGFyZSB5b3VyIHJlc3VsdHMuIAoKCiFbXShpbWcvZGF0YS1zY2llbmNlLWV4cGxvcmUucG5nKQoKPC9icj4KCkhvdyBkbyB3ZSBnZXQgdGhlcmU/IFRvZGF5IHdlIGFyZSBnb2luZyB0byBiZSBsZWFybmluZyBob3cgdG8gbWFrZSBhbGwgc29ydHMgb2YgcGxvdHMgLSBmcm9tIHNpbXBsZSBkYXRhIGV4cGxvcmF0aW9uIHRvIGludGVyYWN0aXZlIHBsb3RzLlRoZSBuZXh0IGxlc3NvbiB3aWxsIGJlIGRhdGEgY2xlYW5pbmcgYW5kIHN0cmluZyBtYW5pcHVsYXRpb247IHRoaXMgaXMgcmVhbGx5IHRoZSBiYXR0bGVncm91bmQgb2YgY29kaW5nIC0gZ2V0dGluZyB5b3VyIGRhdGEgaW50byB0aGUgZm9ybWF0IHdoZXJlIHlvdSBjYW4gYW5hbHlzZSBpdC4gVGhlbiB3ZSB3aWxsIGxlYXJuIGhvdyB0byBkbyB0LXRlc3RzIGFuZCBwZXJmb3JtIHJlZ3Jlc3Npb24gYW5kIG1vZGVsaW5nIGluIFIuIEFuZCBsYXN0bHksIHdlIHdpbGwgbGVhcm4gdG8gd3JpdGUgc29tZSBmdW5jdGlvbnMsIHdoaWNoIHJlYWxseSBjYW4gc2F2ZSB5b3UgdGltZSBhbmQgaGVscCBzY2FsZSB1cCB5b3VyIGFuYWx5c2VzLgoKCiFbXShpbWcvc3BvdGlmeS1ob3d0b2J1aWxkbXZwLmdpZikKCjwvYnI+CgpUaGUgc3RydWN0dXJlIG9mIHRoZSBjbGFzcyBpcyBhIGNvZGUtYWxvbmcgc3R5bGUuIEl0IGlzIGhhbmRzIG9uLiBUaGUgbGVjdHVyZSBBTkQgY29kZSB3ZSBhcmUgZ29pbmcgdGhyb3VnaCBhcmUgYXZhaWxhYmxlIG9uIEdpdEh1YiBmb3IgZG93bmxvYWQgYXQgaHR0cHM6Ly9naXRodWIuY29tL2VhY3Rvbi9DQUdFRiBfXyhOb3RlOiByZXBvIGlzIHByaXZhdGUgdW50aWwgYXBwcm92ZWQpX18sIHNvIHlvdSBjYW4gc3BlbmQgdGhlIHRpbWUgY29kaW5nIGFuZCBub3QgdGFraW5nIG5vdGVzLiBBcyB3ZSBnbyBhbG9uZywgdGhlcmUgd2lsbCBiZSBzb21lIGNoYWxsZW5nZSBxdWVzdGlvbnMgYW5kIG11bHRpcGxlIGNob2ljZSBxdWVzdGlvbnMgb24gU29jcmF0aXZlLiBBdCB0aGUgZW5kIG9mIHRoZSBjbGFzcyBpZiB5b3UgY291bGQgcGxlYXNlIGZpbGwgb3V0IGEgcG9zdC1sZXNzb24gc3VydmV5IChodHRwczovL3d3dy5zdXJ2ZXltb25rZXkuY29tL3IvVk5RWjNLUyksIGl0IHdpbGwgaGVscCBtZSBmdXJ0aGVyIGRldmVsb3AgdGhpcyBjb3Vyc2UgYW5kIHdvdWxkIGJlIGdyZWF0bHkgYXBwcmVjaWF0ZWQuIAoKKioqCgojIyMjUGFja2FnZXMgVXNlZCBpbiBUaGlzIExlc3NvbgoKVGhlIGZvbGxvd2luZyBwYWNrYWdlcyBhcmUgdXNlZCBpbiB0aGlzIGxlc3NvbjoKCmB0aWR5dmVyc2VgIChgZ2dwbG90MmAsIGB0aWR5cmAsIGBkcGx5cmApICAgICAKKGB0d2l0dGVSYCkqCmB0aWR5dGV4dGAgICAgIApgdmlyaWRpc2AgICAgIAoKKlVzZWQgdG8gZ2VuZXJhdGUgdGhlIHR3ZWV0IHRhYmxlcyB1c2VkIGluIHRoaXMgbGVzc29uLiBJdCBpcyBub3QgbmVjZXNzYXJ5IGZvciB5b3UgdG8gaW5zdGFsbCB0aGlzIC0geW91IGNhbiB3b3JrIGZyb20gdGhlIHRhYmxlcy4gSWYgeW91IHdhbnQgdG8gY3JlYXRlIHRoZXNlIGZpbGVzIC0gdGhlIGNvZGUgaXMgaGVyZSAuLi4uLi4uLi4uLi4uLi4uKGluc2VydCBsaW5rKS4gICAgCgpQbGVhc2UgaW5zdGFsbCBhbmQgbG9hZCB0aGVzZSBwYWNrYWdlcyBmb3IgdGhlIGxlc3Nvbi4gSW4gdGhpcyBkb2N1bWVudCBJIHdpbGwgbG9hZCBlYWNoIHBhY2thZ2Ugc2VwYXJhdGVseSwgYnV0IEkgd2lsbCBub3QgYmUgcmVtaW5kaW5nIHlvdSB0byBpbnN0YWxsIHRoZSBwYWNrYWdlLiBSZW1lbWJlcjogdGhlc2UgcGFja2FnZXMgbWF5IGJlIGZyb20gQ1JBTiBPUiBCaW9jb25kdWN0b3IuIAoKKioqCiMjIyNIaWdobGlnaHRpbmcKCmBncmV5IGJhY2tncm91bmRgIC0gYSBwYWNrYWdlIG9yIGZ1bmN0aW9uIG9yIGNvZGUgICAgICAKKml0YWxpY3MqIC0gYW4gaW1wb3J0YW50IHRlcm0gb3IgY29uY2VwdCAgICAgCioqYm9sZCoqIC0gaGVhZGluZyBvciAnZ3JhbW1hciBvZiBncmFwaGljcycgdGVybSAgICAgIAoKKioqCl9fT2JqZWN0aXZlOl9fIEF0IHRoZSBlbmQgb2YgdGhpcyBzZXNzaW9uIHlvdSB3aWxsIGJlIGFibGUgdG8gdXNlIHJlZ3VsYXIgZXhwcmVzc2lvbnMgdG8gJ2NsZWFuJyB5b3VyIGRhdGEuIFlvdSB3aWxsIGFsc28gbGVhcm4gUiBtYXJrZG93biBhbmQgYmUgYWJsZSB0byByZW5kZXIgeW91ciBSIGNvZGUgaW50byBzbGlkZXMsIGEgcGRmLCBodG1sLCBhIHdvcmQgZG9jdW1lbnQsIG9yIGEgbm90ZWJvb2suCgoqKioKCiMjRGF0YSBDbGVhbmluZyBvciBEYXRhIE11bmdpbmcgb3IgRGF0YSBXcmFuZ2xpbmcKCldoeSBkbyB3ZSBuZWVkIHRvIGRvIHRoaXM/CgonUmF3JyBkYXRhIGlzIHNlbGRvbSAobmV2ZXIpIGluIGEgdXNlYWJsZSBmb3JtYXQuIERhdGEgaW4gdHV0b3JpYWxzIG9yIGRlbW9zIGhhcyBhbHJlYWR5IGJlZW4gbWV0aWN1bG91c2x5IGZpbHRlcmVkLCB0cmFuc2Zvcm1lZCBhbmQgcmVhZGllZCBmb3IgdGhhdCBzcGVjaWZpYyBhbmFseXNpcy4gSG93IG1hbnkgcGVvcGxlIGhhdmUgZG9uZSBhIHR1dG9yaWFsIG9ubHkgdG8gZmluZCB0aGV5IGNhbid0IGdldCB0aGVpciBvd24gZGF0YSBpbiB0aGUgQColKCQhIGZvcm1hdCB0byB1c2UgdGhlIHRvb2wgdGhleSBoYXZlIGp1c3Qgc3BlbmQgYW4gaG91ciBsZWFybmluZyBhYm91dD8/PwoKRGF0YSBjbGVhbmluZyByZXF1aXJlcyB1cyB0bzoKCi0gZ2V0IHJpZCBvZiBpbmNvbnNpc3RlbmNpZXMgaW4gb3VyIGRhdGEuIAotIGhhdmUgbGFiZWxzIHRoYXQgbWFrZSBzZW5zZS4gCi0gY2hlY2sgZm9yIGludmFsaWQgY2hhcmFjdGVyL251bWVyaWMgdmFsdWVzLgotIGNoZWNrIGZvciBpbmNvbXBsZXRlIGRhdGEuCi0gcmVtb3ZlIGRhdGEgd2UgZG8gbm90IG5lZWQuCi0gZ2V0IG91ciBkYXRhIGluIGEgcHJvcGVyIGZvcm1hdCB0byBiZSBhbmFseXplZCBieSB0aGUgdG9vbHMgd2UgYXJlIHVzaW5nLiAKLSBmbGFnL3JlbW92ZSBkYXRhIHRoYXQgZG9lcyBub3QgbWFrZSBzZW5zZS4KClNvbWUgZGVmaW5pdGlvbnMgbWlnaHQgdGFrZSB0aGlzIGEgYml0IGZhcnRoZXIgYW5kIGluY2x1ZGUgbm9ybWFsaXppbmcgZGF0YSBhbmQgcmVtb3Zpbmcgb3V0bGllcnMsIGJ1dCBJIGNvbnNpZGVyIGRhdGEgY2xlYW5pbmcgYXQgbGVhc3QsIGdldHRpbmcgZGF0YSBpbnRvIGEgZm9ybWF0IHdoZXJlIHdlIGNhbiBzdGFydCBhY3RpdmVseSBkb2luZyAndGhlIG1hdGhzIG9yIHRoZSBncmFwaHMnIHdoZXRoZXIgaXQgYmUgc3RhdGlzdGljYWwgY2FsY3VsYXRpb25zLCBub3JtYWxpemF0aW9uIG9yIGV4cGxvcmF0b3J5IHBsb3RzLiBXZSBoYXZlIGxlYXJuZWQgaG93IHRvIHRyYW5zZm9ybSBkYXRhIGludG8gYSB0aWR5IGZvcm1hdCBpbiBMZXNzb24gMiwgYnV0IHRoZSBwcmVsdWRlIHRvIHRyYW5zZm9ybWluZyBkYXRhIGlzIGRvaW5nIHRoZSBncnVudCB3b3JrIG1lbnRpb25lZCBhYm92ZS4gU28gbGV0J3MgZ2V0IHRvIGl0IQoKIVtdKGltZy9jbGVhbmluZy5naWYpCjwvYnI+CgoKCgojI0ludHJvIHRvIHJlZ3VsYXIgZXhwcmVzc2lvbnMKCgoqKlJlZ3VsYXIgZXhwcmVzc2lvbnMqKgoKIkEgR29kLWF3ZnVsIGFuZCBwb3dlcmZ1bCBsYW5ndWFnZSBmb3IgZXhwcmVzc2luZyBwYXR0ZXJucyB0byBtYXRjaCBpbiB0ZXh0IG9yIGZvciBzZWFyY2gtYW5kLXJlcGxhY2UuIEZyZXF1ZW50bHkgZGVzY3JpYmVkIGFzICd3cml0ZSBvbmx5JywgYmVjYXVzZSByZWd1bGFyIGV4cHJlc3Npb25zIGFyZSBlYXNpZXIgdG8gd3JpdGUgdGhhbiB0byByZWFkL3VuZGVyc3RhbmQuIEFuZCB0aGV5IGFyZSBub3QgcGFydGljdWxhcmx5IGVhc3kgdG8gd3JpdGUuIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtIEplbm55IEJyeWFuCgohW10oaW1nL3hrY2QtMTE3MS1wZXJsX3Byb2JsZW1zLnBuZykKCjwvYnI+CgpTbyB3aHkgZG9lcyByZWdleCBnZXQgc28gbXVjaCBmbGFrPwoKU2NhcnkgZXhhbXBsZTogaG93IHRvIGdldCBhbiBlbWFpbCBpbiBkaWZmZXJlbnQgcHJvZ3JhbW1pbmcgbGFuZ3VhZ2VzIChodHRwOi8vZW1haWxyZWdleC5jb20vKS4gV2hhdGV2ZXIuIAoKCiFbXShpbWcvODAxNzBjMTE5OTZiZDU4ZTQyMmRiYjY2MzFiNzNjNGIuanBnKSAKCiFbXShpbWcvcmVnZXhieXRyaWFsYW5kZXJyb3ItYmlnLXNtYWxsZXIucG5nKQoKPC9icj4KCldoYXQgZG9lcyB0aGUgbGFuZ3VhZ2UgbG9vayBsaWtlPwoKCl9NYXRjaGluZyBieSBwb3NpdGlvbl8KYGBge3J9CgpgYGAKCgpfUXVhbnRpZmllcnNfCgpgYGB7cn0KCmBgYAoKCl9DaGFyYWN0ZXIgY2xhc3Nlc18KCmBgYHtyfQoKYGBgCgoKX09wZXJhdG9yc18KYGBge3J9CgpgYGAKCl9Fc2NhcGUgY2hhcmFjdGVyc18KYGBge3J9CgpgYGAKCiMjRGF0YSBDbGVhbmluZyB3aXRoIEJhc2UgUiAoQUtBIFdoYXQgaXMgRWxvbiBNdXNrIHVwIHRvIGFueXdheXM/KQoKTGV0J3MgcGVyZm9ybSBzb21lIGJhc2ljIGRhdGEgY2xlYW5pbmcgdGFza3Mgd2l0aCBhbiBhY3R1YWwgKGZ1bj8pIG1lc3N5IGRhdGEgc2V0LiBJIGhhdmUgc2NyYXBlZCBFbG9uIE11c2sncyBsYXRlc3QgdHdlZXRzIGZyb20gVHdpdHRlci4gVGhlIGNvZGUgdG8gZG8gdGhpcyBpcyBpbiB0aGUgTGVzc29uIDQgbWFya2Rvd24gZmlsZSBpZiB5b3UgYXJlIGN1cmlvdXMgYW5kL29yIHdhbnQgdG8gY3JlZXAgc29tZW9uZSBvbiBUd2l0dGVyLgoKYGBge3IgZWNobyA9IEZBTFNFIH0KbGlicmFyeSh0d2l0dGVSKQpsaWJyYXJ5KGh0dHIpCiNUaGlzIGlzIGZyb20gdGhlIGNvZGUgZGVtbyBmb3IgaHR0cjo6b2F1dGgxLXR3aXR0ZXIKIyAxLiBGaW5kIE9BdXRoIHNldHRpbmdzIGZvciB0d2l0dGVyOgpvYXV0aF9lbmRwb2ludHMoInR3aXR0ZXIiKQoKIyAyLiBSZWdpc3RlciBhbiBhcHBsaWNhdGlvbiBhdCBodHRwczovL2FwcHMudHdpdHRlci5jb20vCiMgICAgTWFrZSBzdXJlIHRvIHNldCBjYWxsYmFjayB1cmwgdG8gImh0dHA6Ly8xMjcuMC4wLjE6MTQxMC8iCiMKIyAgICBSZXBsYWNlIGtleSBhbmQgc2VjcmV0IGJlbG93Cm15YXBwIDwtIG9hdXRoX2FwcCgidHdpdHRlciIsCiAga2V5ID0gIlRZcldGUGtGQWtuNEc1QmJrV0lOWXciLAogIHNlY3JldCA9ICJxak9rbUtZVTlrV2ZVRldtZWtKdXU1dHp0RTlhRWZMYnQyNldsaFpMOCIKKQoKIyAzLiBHZXQgT0F1dGggY3JlZGVudGlhbHMKdHdpdHRlcl90b2tlbiA8LSBvYXV0aDEuMF90b2tlbihvYXV0aF9lbmRwb2ludHMoInR3aXR0ZXIiKSwgbXlhcHApCgojIDQuIFVzZSBBUEkgLSBub3cgdGhlIGNvZGUgZGl2ZXJnZXMgZnJvbSB0aGUgZGVtbwpzZXR1cF90d2l0dGVyX29hdXRoKGNvbnN1bWVyX2tleSA9IHR3aXR0ZXJfdG9rZW5bWyJhcHAiXV1bWyJrZXkiXV0gLCBjb25zdW1lcl9zZWNyZXQgPSB0d2l0dGVyX3Rva2VuW1siYXBwIl1dW1sic2VjcmV0Il1dLCBhY2Nlc3NfdG9rZW4gPSB0d2l0dGVyX3Rva2VuW1siY3JlZGVudGlhbHMiXV1bWyJvYXV0aF90b2tlbiJdXSwgYWNjZXNzX3NlY3JldCA9IHR3aXR0ZXJfdG9rZW5bWyJjcmVkZW50aWFscyJdXVtbIm9hdXRoX3Rva2VuX3NlY3JldCJdXSApCgojMzIwMCBpcyB0aGUgbWF4IG51bWJlciBvZiB0d2VldHMgcmVxdWVzdGFibGUKZWxvbl90d2VldHMgPC0gdXNlclRpbWVsaW5lKCJlbG9ubXVzayIsIG4gPSAzMjAwKQplbG9uX3R3ZWV0c19kZiA8LSB0YmxfZGYobWFwX2RmKGVsb25fdHdlZXRzLCBhcy5kYXRhLmZyYW1lKSkKCndyaXRlLnRhYmxlKGVsb25fdHdlZXRzX2RmLCAiZGF0YS9lbG9uX3R3ZWV0c19kZi50eHQiLCBzZXAgPSAiXHQiKQoKI3VzZWQgdGhlIGZvbGxvd2luZyBoYW5kbGVzIGMoZWxvbiA9ICJlbG9ubXVzayIsIG55ZSA9ICJCaWxsTnllIiwganQgPSAiSnVzdGluVHJ1ZGVhdSIsIGNvbGJlcnQgPSAiU3RlcGhlbkF0SG9tZSIsIGplbmdhcmR5ID0gIkplbm5pZmVyR2FyZHkiLCBqZW5ueSA9ICJKZW5ueUJyeWFuIiwga2F0eSA9ICJLYXR5UGVycnkiLCBkYWlseSA9ICJUaGVEYWlseVNob3ciLCBqaW1teSA9ICJKaW1teUZhbGxvbiIsIHRydW1wID0gInJlYWxEb25hbGRUcnVtcCIpCmBgYAoKTGV0J3MgcmVhZCBpbiB0aGUgc2V0IG9mIHR3ZWV0cywgdGFrZSBhIGxvb2sgYXQgdGhlIHN0cnVjdHVyZSBvZiB0aGUgZGF0YSwgYW5kIHVzZSAndGlkeXZlcnNlJyB0byBvcmRlciB0aGUgZGF0YSBieSB0aGUgbW9zdCBwb3B1bGFyIChmYXZvcml0ZWQpIHR3ZWV0cy4gTGV0J3MgY2hlY2sgb3V0IHRoZSB0b3AgNSBmYXZvcml0ZSB0d2VldHMuCgpgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpCgplbG9uX3R3ZWV0c19kZiA8LSByZWFkLmRlbGltKCJkYXRhL2Vsb25fdHdlZXRzX2RmLnR4dCIsIHNlcCA9ICJcdCIpCnN0cihlbG9uX3R3ZWV0c19kZikKCmVsb25fdHdlZXRzX2RmIDwtIGVsb25fdHdlZXRzX2RmICU+JSBhcnJhbmdlKGRlc2MoZmF2b3JpdGVDb3VudCkpCmVsb25fdHdlZXRzX2RmJHRleHRbMTo1XQoKCmBgYApPdXIgZW5kIGdvYWwgaXMgZ29pbmcgdG8gYmUgdG8gbG9vayBhdCB0aGUgdG9wIDUwIHdvcmRzIGluIEVsb24gTXVzaydzIHR3ZWV0cy4gSSBlbXBoYXNpemUgd29yZHMsIGJlY2F1c2UgSSBkb24ndCB3YW50IHVybHMsIG9yIGhhc3RhZ3MsIG9yIG90aGVyIHRhZ3MuIEkgYWxzbyBkb24ndCB3YW50IHB1bmN0dWF0aW9uIG9yIHNwYWNlcy4gSSB3YW50IHRvIGV4dHJhY3QganVzdCB0aGUgd29yZHMgZnJvbSB0d2VldHMuIEZpcnN0LCBJIHdhbnQgdG8gZ2V0IHJlbW92ZSB0aGUgdGFncyBmcm9tIHRoZSBiZWdpbm5pbmcgb2Ygd29yZHMuIEJlY2F1c2UgcmVnZXggZXhwcmVzc2lvbnMgY2FuIGdldCAndWdseScsIEkgYW0gZ29pbmcgdG8gc2F2ZSBteSByZWdleCBleHByZXNzaW9uIGludG8gYW4gb2JqZWN0IC0gdGhhdCB3YXkgSSBhbSB0eXBpbmcgc29tZXRoaW5nIGxpa2UgYHRhZ3NgIHJlcGVhdGVkbHkgaW5zdGVhZCBvZiBgI3xAXFx3K2AgcmVwZWF0ZWRseS4gCgpXaGF0IHRoaXMgZXhwcmVzc2lvbiBzYXlzIGlzIHRoYXQgSSB3YW50IHRvIGZpbmQgbWF0Y2hlcyBmb3IgYSBoYXN0YWcgT1IgYW4gYXNwZXJhbmQgZm9sbG93ZWQgYnkgYXQgbGVhc3Qgb25lIHdvcmQgY2hhcmFjdGVyLgoKYGBge3J9CnRhZ3MgPC0gIiN8QFxcdysiCgojZWxvbiBhcHBhcmVudGx5IGRvZXNuJ3QgdXNlIGhhc2h0YWdzCgpncmVwKHRhZ3MsIGVsb25fdHdlZXRzX2RmJHRleHQpCgpgYGAKT2theS4gYGdyZXBgIHJldHVybnMgdGhlIGluZGV4IG9mIHRoZSBtYXRjaC4gV2UgaGF2ZSBhIG51bWJlciBvZiBlbnRyaWVzIHRoYXQgaW5jbHVkZSB0YWdzLiBIb3dldmVyLCB3ZSBhcmUgaW1tZWRpYXRlbHkgZ2V0dGluZyBhbiBlcnJvciBmb3IgbGluZXMgMTAsIDExOCwgMTU2LCAyMTkgYW5kIDIyNCBiZWluZyAnaW52YWxpZCcuIFRoaXMgaXMgYSBnb29kIG9wcG9ydHVuaXR5IGZvciB0cm91YmxlLXNob290aW5nLiBXaGF0IHdvdWxkIHlvdSBkbyBuZXh0PyBMZXQncyBjaGVjayBvdXQgdGhvc2UgbGluZXMuCgpgYGB7cn0KCmVsb25fdHdlZXRzX2RmW2MoMTAsMTg4LDE1NiwyMTksMjI0KSwgInRleHQiXQoKYGBgCkkgdGhpbmsgdGhlc2UgYXJlIGVtb2ppcy4gV2hhdCB3b3VsZCB5b3UgZG8gbmV4dD8gU2luY2UgdGhlc2UgYXJlIG5vdCB3b3JkcywgdGhleSBzaG91bGQgYmUgcmVtb3ZlZC4gClNpbmNlIHdlIGFyZSBnZXR0aW5nIGFuIGludmFsaWQgbG9jYWxlIGNhbGwsIGxldCdzIGNoZWNrIG91dCBvdXIgbG9jYWxlLgoKYGBge3J9ClN5cy5nZXRsb2NhbGUoKQpgYGAKTG9va3MgbGlrZSB3ZSBhcmUgc2V0IGluIENhbmFkYS4gTGV0J3MgdHVybiBvZmYgbG9jYWxlLXNwZWNpZmljIHNldHRpbmdzLgoKYGBge3J9ClN5cy5zZXRsb2NhbGUoJ0xDX0FMTCcsJ0MnKQpgYGAKSWYgd2UgcnVuIG91ciB0YWdzIGNvZGUgYWdhaW4sIHdlIG5vdyBkb24ndCBnZXQgYW4gZXJyb3IgYW5kIGxpbmUgMTg4LCB3aGljaCB3ZSBjYW4gc2VlIGhhcyBhIHRhZywgaXMgaW5jbHVkZWQuIAoKYGBge3J9CmdyZXAodGFncywgZWxvbl90d2VldHNfZGYkdGV4dCkKYGBgCkluIG9yZGVyIHRvIHJlYWxseSBjaGVjayB0aGF0IGVhY2ggbGluZSBpbmRlZWQgaGFzIGEgdGFnLCB3ZSBjYW4gc2V0IGB2YWx1ZSA9IFRSVUVgIGluIGdyZXAsIGFuZCBpbnN0ZWFkIHdlIHdpbGwgYmUgcmV0dXJuZWQgdGhlIHRleHQgdGhhdCBpcyBhdCB0aGF0IHBvc2l0aW9uLiBWaXN1YWxseSBpbnNwZWN0aW5nLCBpdCBsb29rcyBsaWtlIGVhY2ggdHdlZXQgaGFzIGEgdGFnLgoKYGBge3J9CmdyZXAodGFncywgZWxvbl90d2VldHNfZGYkdGV4dCwgdmFsdWUgPSBUUlVFKQpgYGAKCgpUaGlzIHJlbWluZHMgbWUgdGhhdCB3ZSBzaG91bGQgYWxzbyByZW1vdmUgZW1vamlzLgoKIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjI2xlZnQgb2ZmIGhlcmUKYGBge3J9CmVtb2ppcyA8LSAiXFxcXHd7M30iCmVtb2ppcyA8LSAiXFx4ZWQiCgpncmVwKHRhZ3MsIG55ZV90d2VldHNfZGYkdGV4dCwgdmFsdWUgPSBUUlVFKQoKZ3JlcGwodGFncywgbnllX3R3ZWV0c19kZiR0ZXh0KQoKCiN0byByZXR1cm4gdGhlIGRhdGEgZnJhbWUgCm55ZV90YWdzIDwtIG55ZV90d2VldHNfZGYgJT4lIGZpbHRlcihncmVwbCh0YWdzLCBueWVfdHdlZXRzX2RmJHRleHQpKQpqdF90YWdzIDwtIGp0X3R3ZWV0c19kZiAlPiUgZmlsdGVyKGdyZXBsKHRhZ3MsIGp0X3R3ZWV0c19kZiR0ZXh0KSkKYGBgCgoKIyNEYXRhIENsZWFuaW5nIHdpdGggc3RyaW5nci9zdHJpbmdpIChBS0EgV2hhdCBpcyBUcnVtcCB1cCB0byBhbnl3YXlzPykKCi0gc2VhcmNoaW5nIGZvciBhIHdvcmQvcGF0dGVybnMsIHN1YnNldCB1c2luZyBjaGFyYWN0ZXIgc3RyaW5ncywgY29sbGFwc2UgYW5kIGV4cGFuZCBjaGFyYWN0ZXIgdmVjdG9ycywgcmVwbGFjZW1lbnQsIHJlcGxhY2luZyBOQXMsIHNwbGl0dGluZy9jb21iaW5pbmcgYXQgYSBkZWxpbWl0ZXIKCmBgYHtyfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKCmVsb25fdHdlZXRzX2RmIDwtIHJlYWQuZGVsaW0oImRhdGEvZWxvbl90d2VldHNfZGYudHh0Iiwgc2VwID0gIlx0IikKc3RyKGVsb25fdHdlZXRzX2RmKQoKCmVsb25fdHdlZXRzX2RmIDwtIGVsb25fdHdlZXRzX2RmICU+JSBhcnJhbmdlKGRlc2MoYXMubnVtZXJpYyhyZXR3ZWV0Q291bnQpKSkKZWxvbl90d2VldHNfZGYgPC0gZWxvbl90d2VldHNfZGYgJT4lIGFycmFuZ2UoZGVzYyhmYXZvcml0ZUNvdW50KSkKZWxvbl90d2VldHNfZGYkdGV4dFsxOjVdCgpueWVfdHdlZXRzIDwtIHVzZXJUaW1lbGluZSgiQmlsbE55ZSIsIG4gPSAzMjAwKQpueWVfdHdlZXRzX2RmIDwtIHRibF9kZihtYXBfZGYobnllX3R3ZWV0cywgYXMuZGF0YS5mcmFtZSkpCgp3cml0ZS50YWJsZShueWVfdHdlZXRzX2RmLCAiZGF0YS9ueWVfdHdlZXRzX2RmLnR4dCIsIHNlcCA9ICJcdCIpCm55ZV90d2VldHNfZGYgPC0gcmVhZC5kZWxpbSgiZGF0YS9ueWVfdHdlZXRzX2RmLnR4dCIsIHNlcCA9ICJcdCIpCgpueWVfdHdlZXRzX2RmIDwtIG55ZV90d2VldHNfZGYgJT4lIGFycmFuZ2UoZGVzYyhhcy5udW1lcmljKHJldHdlZXRDb3VudCkpKQpueWVfdHdlZXRzX2RmIDwtIG55ZV90d2VldHNfZGYgJT4lIGFycmFuZ2UoZGVzYyhmYXZvcml0ZUNvdW50KSkKbnllX3R3ZWV0c19kZiR0ZXh0WzE6NV0KCmp0X3R3ZWV0cyA8LSB1c2VyVGltZWxpbmUoIkp1c3RpblRydWRlYXUiLCBuID0gMzIwMCkKanRfdHdlZXRzX2RmIDwtIHRibF9kZihtYXBfZGYoanRfdHdlZXRzLCBhcy5kYXRhLmZyYW1lKSkKCndyaXRlLnRhYmxlKGp0X3R3ZWV0c19kZiwgImRhdGEvanRfdHdlZXRzX2RmLnR4dCIsIHNlcCA9ICJcdCIpCmp0X3R3ZWV0c19kZiA8LSByZWFkLmRlbGltKCJkYXRhL2p0X3R3ZWV0c19kZi50eHQiLCBzZXAgPSAiXHQiKQoKanRfdHdlZXRzX2RmIDwtIGp0X3R3ZWV0c19kZiAlPiUgYXJyYW5nZShkZXNjKGFzLm51bWVyaWMocmV0d2VldENvdW50KSkpCmp0X3R3ZWV0c19kZiA8LSBqdF90d2VldHNfZGYgJT4lIGFycmFuZ2UoZGVzYyhmYXZvcml0ZUNvdW50KSkKanRfdHdlZXRzX2RmJHRleHRbMTo1XQoKY29sYmVydF90d2VldHMgPC0gdXNlclRpbWVsaW5lKCJTdGVwaGVuQXRIb21lIiwgbiA9IDMyMDApCmNvbGJlcnRfdHdlZXRzX2RmIDwtIHRibF9kZihtYXBfZGYoY29sYmVydF90d2VldHMsIGFzLmRhdGEuZnJhbWUpKQoKd3JpdGUudGFibGUoY29sYmVydF90d2VldHNfZGYsICJkYXRhL2NvbGJlcnRfdHdlZXRzX2RmLnR4dCIsIHNlcCA9ICJcdCIpCmNvbGJlcnRfdHdlZXRzX2RmIDwtIHJlYWQuZGVsaW0oImRhdGEvY29sYmVydF90d2VldHNfZGYudHh0Iiwgc2VwID0gIlx0IikKCmNvbGJlcnRfdHdlZXRzX2RmIDwtIGNvbGJlcnRfdHdlZXRzX2RmICU+JSBhcnJhbmdlKGRlc2MoYXMubnVtZXJpYyhyZXR3ZWV0Q291bnQpKSkKY29sYmVydF90d2VldHNfZGYgPC0gY29sYmVydF90d2VldHNfZGYgJT4lIGFycmFuZ2UoZGVzYyhmYXZvcml0ZUNvdW50KSkKY29sYmVydF90d2VldHNfZGYkdGV4dFsxOjVdCmNvbGJlcnRfdHdlZXRzX2RmJHRleHRbNToxMF0KCmRhaWx5X3R3ZWV0cyA8LSB1c2VyVGltZWxpbmUoIlRoZURhaWx5U2hvdyIsIG4gPSAzMjAwKQpkYWlseV90d2VldHNfZGYgPC0gdGJsX2RmKG1hcF9kZihkYWlseV90d2VldHMsIGFzLmRhdGEuZnJhbWUpKQoKd3JpdGUudGFibGUoZGFpbHlfdHdlZXRzX2RmLCAiZGF0YS9kYWlseV90d2VldHNfZGYudHh0Iiwgc2VwID0gIlx0IikKZGFpbHlfdHdlZXRzX2RmIDwtIHJlYWQuZGVsaW0oImRhdGEvZGFpbHlfdHdlZXRzX2RmLnR4dCIsIHNlcCA9ICJcdCIpCgpkYWlseV90d2VldHNfZGYgPC0gZGFpbHlfdHdlZXRzX2RmICU+JSBtdXRhdGUocmV0d2VldENvdW50ID0gYXMubnVtZXJpYyhyZXR3ZWV0Q291bnQpKSAlPiUgYXJyYW5nZShkZXNjKHJldHdlZXRDb3VudCkpCmRhaWx5X3R3ZWV0c19kZiA8LSBkYWlseV90d2VldHNfZGYgJT4lIGFycmFuZ2UoZGVzYyhmYXZvcml0ZUNvdW50KSkKZGFpbHlfdHdlZXRzX2RmJHRleHRbMTo1XQpkYWlseV90d2VldHNfZGYkdGV4dFs4OjEyXQoKdHJ1bXBfdHdlZXRzIDwtIHVzZXJUaW1lbGluZSgicmVhbERvbmFsZFRydW1wIiwgbiA9IDMyMDApCnRydW1wX3R3ZWV0c19kZiA8LSB0YmxfZGYobWFwX2RmKHRydW1wX3R3ZWV0cywgYXMuZGF0YS5mcmFtZSkpCgp3cml0ZS50YWJsZSh0cnVtcF90d2VldHNfZGYsICJkYXRhL3RydW1wX3R3ZWV0c19kZi50eHQiLCBzZXAgPSAiXHQiKQp0cnVtcF90d2VldHNfZGYgPC0gcmVhZC5kZWxpbSgiZGF0YS90cnVtcF90d2VldHNfZGYudHh0Iiwgc2VwID0gIlx0IikKCnRydW1wX3R3ZWV0c19kZiA8LSB0cnVtcF90d2VldHNfZGYgJT4lIG11dGF0ZShyZXR3ZWV0Q291bnQgPSBhcy5udW1lcmljKHJldHdlZXRDb3VudCkpICU+JSBhcnJhbmdlKGRlc2MocmV0d2VldENvdW50KSkKdHJ1bXBfdHdlZXRzX2RmIDwtIHRydW1wX3R3ZWV0c19kZiAlPiUgYXJyYW5nZShkZXNjKGZhdm9yaXRlQ291bnQpKQp0cnVtcF90d2VldHNfZGYkdGV4dFsxOjVdCnRydW1wX3R3ZWV0c19kZiR0ZXh0Wzg6MTJdCmBgYAoKCgotcm91bmRpbmcgZGF0YSAtIHdlIGRvbid0IG5lZWQgaXQgdG8gdGhlIHVtcHRlZW50aCBkZWNpbWFsCi1sb29rIGZvciBsb3dlc3QgYW5kIGhpZ2hlc3QgdmFsdWVzIC0gZG8gdGhlc2UgbWFrZSBzZW5zZT8gKHN0YW5kYXJkIGRldmlhdGlvbj8pCi1jYWxsZWQgYSByYW5nZSBjaGVjaywgc3BlbGwgY2hlY2ssIHJlZ2V4Ci1kb2N1bWVudCB3aGF0IHlvdSBhcmUgZG9pbmcKLW5vIGFsbCBkYXRhIHNldHMgYXJlIDEwMCUgY2xlYW4KLWFsc28gdGhlIHBhc3RlIGZ1bmN0aW9ucwoKLXRoZSBiYXNpY3MgcnVubmluZyB0aHJvdWdoIGEgZnVuIGV4YW1wbGUKLSBkbyBhIGNob3NlbiBvbmUsIHNlZSB3aGF0IG90aGVyIHByb2JsZW1zIGNvbWUgdXAKLXJtYXJrZG93biwgc3ludGF4LCBldGMKLW1ha2UgYSBwZGYgb2YgY2xlYW5pbmcgdGhlIFdlbGxjb21lVHJ1c3QgZGF0YXNldCAoZ2l2ZSBhIGJyaWVmIG91dGxpbmUgb2Ygd2hhdCBuZWVkcyB0byBiZSBkb25lKQoKCgoKCmtpZXJhbiBoYWQgYSByZWFsbHkgZ29vZCBsZXNzb24gdXNpbmcgdHdpdHRlcgpodHRwOi8vc3RhdDU0NS5jb20vYmxvY2swMjdfcmVndWxhci1leHByZXNzaW9ucy5odG1sCgpqZW5ueSdzIGxlc3NvbiBnb29kIGNvbnRlbnQgYnV0IGtpbmQgb2YgYm9yaW5nCmh0dHA6Ly9zdGF0NTQ1LmNvbS9ibG9jazAyOF9jaGFyYWN0ZXItZGF0YS5odG1sCgoKLS1nb29kIGNvbnRlbnQgYnV0IHNpY2sgb2YgdHJ1bXAKCmdldHRpbmcgdHdlZXRzIGFuZCBiYXNpYyBzdGF0cwpodHRwOi8vdmFyaWFuY2VleHBsYWluZWQub3JnL3IvdHJ1bXAtdHdlZXRzLwoKCnJlbWVtYmVyIHJlZ2V4IHRlc3RlcnMKaHR0cHM6Ly9yZWdleDEwMS5jb20vCmh0dHBzOi8vcmVnZXhyLmNvbS8KCgoKCgoKCgpTb21ldGhpbmcgbm9ybWFsIHRvIGRvIGluIHR3aXR0ZXIgd291bGQgYmUgdG8gZmluZCBoYXNodGFncy4KCgpgYGB7cn0KdGFncyA8LSAiI1xcdysiCgojZWxvbiBhcHBhcmVudGx5IGRvZXNuJ3QgdXNlIGhhc2h0YWdzCgojbGV0IG1lIGd1ZXNzIHRoYXQgaXQncyBnb2luZyB0byBiZSAnI3NjaWVuY2VydWxlcycKZ3JlcCh0YWdzLCBueWVfdHdlZXRzX2RmJHRleHQpCgpncmVwKHRhZ3MsIG55ZV90d2VldHNfZGYkdGV4dCwgdmFsdWUgPSBUUlVFKQoKZ3JlcGwodGFncywgbnllX3R3ZWV0c19kZiR0ZXh0KQoKCiN0byByZXR1cm4gdGhlIGRhdGEgZnJhbWUgCm55ZV90YWdzIDwtIG55ZV90d2VldHNfZGYgJT4lIGZpbHRlcihncmVwbCh0YWdzLCBueWVfdHdlZXRzX2RmJHRleHQpKQpqdF90YWdzIDwtIGp0X3R3ZWV0c19kZiAlPiUgZmlsdGVyKGdyZXBsKHRhZ3MsIGp0X3R3ZWV0c19kZiR0ZXh0KSkKYGBgCgpHZXQgcmlkIG9mIHVybHMuCgpgYGB7cn0KdXJsIDwtICJodHRwW3NdPzovL1tbOmFsbnVtOl0uXFwvXSsiCgojcmVwbGFjZSB3aXRoIG5vdGhpbmcKCm55ZV90d2VldHNfZGYkdGV4dCA8LSBnc3ViKHBhdHRlcm4gPSAiaHR0cFtzXT86Ly9bWzphbG51bTpdLlxcL10rIiwgcmVwbGFjZW1lbnQgPSAiIiwgbnllX3R3ZWV0c19kZiR0ZXh0KQoKYGBgCgpHZXQgcmlkIG9mIHRyYWlsaW5nIHNwYWNlcy4KCmBgYHtyfQp0cmFpbCA8LSAiWyBdKyQiCgpueWVfdHdlZXRzX2RmJHRleHQgPC0gZ3N1YihwYXR0ZXJuID0gIlsgXSskIiwgcmVwbGFjZW1lbnQgPSAiIiwgbnllX3R3ZWV0c19kZiR0ZXh0KQpgYGAKCgpMZXQncyBicmVhayB0aGUgdGV4dHMgZG93biBpbnRvIGluZGl2aWR1YWwgd29yZHMsIHNvIHdlIGNhbiBzZWUgd2hhdCB0aGUgbW9zdCBjb21tb24gd29yZHMgdXNlZCBhcmUuIAoKCmBgYHtyfQpsaWJyYXJ5KHN0cmluZ3IpCgpzdHJfc3BsaXQobnllX3R3ZWV0c19kZiR0ZXh0LCAiXFxzIiwgc2ltcGxpZnkgPSBUUlVFKVsxLF0KCiNhbnl0aGluZyBmb2xsb3dlZCBieSBhbnl0aGluZyBhbmQgd29yZCBib3VuZGFyeQpzdHJfZXh0cmFjdF9hbGwobnllX3R3ZWV0c19kZiR0ZXh0LCAiKEB8I3xcXGJ8W1s6YWxudW06XV0rWyddKVtbOmFsbnVtOl1dKyIpCiNjb29sCnNhdmUgPC0gc3RyX2V4dHJhY3RfYWxsKG55ZV90d2VldHNfZGYkdGV4dCwgIlsjfEB8WzphbG51bTpdXSsoW15cXHNdW1s6YWxudW06XV0rKT8iLCBzaW1wbGlmeSA9IFRSVUUpCiNnYXRoZXIsIGdldCByaWQgb2YgZW1wdHkgc3RyaW5ncwp0ZXN0IDwtIGdhdGhlcihhcy5kYXRhLmZyYW1lKHNhdmUpKSAlPiUgZmlsdGVyKHZhbHVlICE9ICIiKQoKdGVzdCR2YWx1ZSA8LSB0b2xvd2VyKHRlc3QkdmFsdWUpCgp0ZXN0IDwtIHRlc3QgJT4lIHNlbGVjdCh2YWx1ZSkgJT4lIGNvdW50KGZhY3Rvcih2YWx1ZSkpICU+JSBhcnJhbmdlKGRlc2MobikpCmBgYAoKV293LiBXZSBoYXZlIGRpc2NvdmVyZWQgcGVvcGxlIHVzZSBwcmVwb3NpdGlvbnMgYW5kIGNvbmp1bmN0aW9ucy4gVGhlcmUgaXMgYSBsaXN0IG9mICdzdG9wIHdvcmRzJyB0aGF0IGNhbiBiZSB1c2VkIHRvIGdldCByaWQgb2Ygd29yZHMgdGhhdCBhcmUgdW5saWtlbHkgdG8gY29udGFpbiBpbmZvcm1hdGlvbiBmb3IgdXMgYXMgcGFydCBvZiB0aGUgdGlkeXRleHQgcGFja2FnZS4KCmBgYHtyfQpsaWJyYXJ5KHRpZHl0ZXh0KQpub3BlIDwtIHN0b3Bfd29yZHMKCm55ZV93b3JkcyA8LSBhbnRpX2pvaW4odGVzdCwgbm9wZSwgYnk9YygiZmFjdG9yKHZhbHVlKSIgPSAid29yZCIpKQpgYGAKClRoaXMgbG9va3MgbXVjaCBiZXR0ZXIuIEFuICdhbXAnIGlzIGFuIGFtcGVyc2FuZCAoJikuIEJ1dCBvdGhlcndpc2Ugd2UgZ2V0ICdzY2llbmNlJywgJ3dvcmxkJywgJ3NwYWNlJywgJ2NoYW5nZScsICdqb2luJywgJ2NsaW1hdGUnLCAnbGF1bmNoJywgdm90ZScsICdlYXJ0aCcgYW5kIG90aGVyIHRoaW5ncyB3ZSBtaWdodCBleHBlY3QgZnJvbSB0aGUgc2NpZW5jZSBndXkuIEkgZ3Vlc3MgSmFjayBhbmQgdGhlIEdlbml1c2VzIGlzIGEgYm9vayBzZXJpZXMgYnkgQmlsbCBOeWUuCgooaXQsIGl0cyBhbmQgaXQgaXMgd2VyZSBhbGwgaW4gdGhlIHN0b3AgbGlzdCAtIGl0J3Mgc2hvdWxkIHByb2JhYmx5IGJlIGluIHRoZXJlKS4gQW5kIGl0J3MgaW50ZXJlc3RpbmcgdG8gbm90ZSB0aGVzZSBsaXR0bGUgdmFyaWF0aW9ucyBiZWNhdXNlIG5vIG1hdHRlciBob3cgbXVjaCB5b3UgdHJ5IHRvIGF1dG9tYXRlIHlvdXIgYW5hbHlzaXMgdGhlcmUgaXMgYWx3YXlzIGdvaW5nIHRvIGJlIHNvbWV0aGluZyBmcm9tIHlvdXIgbmV3IGRhdGFzZXQgdGhhdCBkaWRuJ3QgZml0IHdpdGggeW91ciBvbGQgZGF0YXNldC4gVGhpcyBpcyB3aHkgd2UgbmVlZCB0aGVzZSBkYXRhIHdyYW5nbGluZyBza2lsbHMuIEV2ZW4gdGhvdWdoIHNvbWUgcGFja2FnZXMgbWF5IGhhdmUgYmVlbiBjcmVhdGVkIHRvIGhlbHAgdXMgb24gb3VyIHdheSwgdGhleSBjYW4ndCBwb3NzaWJseSBjb3ZlciBldmVyeSBjYXNlLgoKV2hhdCB3aWxsIHdlIGdldCBmcm9tIHRoZSBFbG9uIE11c2s/CgpgYGB7cn0KZWxvbl90d2VldHNfZGYkdGV4dCA8LSBnc3ViKHBhdHRlcm4gPSAiaHR0cFtzXT86Ly9bWzphbG51bTpdLlxcL10rIiwgcmVwbGFjZW1lbnQgPSAiIiwgZWxvbl90d2VldHNfZGYkdGV4dCkKCmVsb25fdHdlZXRzX2RmJHRleHQgPC0gZ3N1YihwYXR0ZXJuID0gIlsgXSskIiwgcmVwbGFjZW1lbnQgPSAiIiwgZWxvbl90d2VldHNfZGYkdGV4dCkKCgpzYXZlIDwtIHN0cl9leHRyYWN0X2FsbChlbG9uX3R3ZWV0c19kZiR0ZXh0LCAiWyN8QHxbOmFsbnVtOl1dKyhbXlxcc11bWzphbG51bTpdXSspPyIsIHNpbXBsaWZ5ID0gVFJVRSkKI2dhdGhlciwgZ2V0IHJpZCBvZiBlbXB0eSBzdHJpbmdzCnRlc3QgPC0gZ2F0aGVyKGFzLmRhdGEuZnJhbWUoc2F2ZSkpICU+JSBmaWx0ZXIodmFsdWUgIT0gIiIpCgp0ZXN0JHZhbHVlIDwtIHRvbG93ZXIodGVzdCR2YWx1ZSkKCnRlc3QgPC0gdGVzdCAlPiUgc2VsZWN0KHZhbHVlKSAlPiUgY291bnQoZmFjdG9yKHZhbHVlKSkgJT4lIGFycmFuZ2UoZGVzYyhuKSkKCmVsb25fd29yZHMgPC0gYW50aV9qb2luKHRlc3QsIG5vcGUsIGJ5PWMoImZhY3Rvcih2YWx1ZSkiID0gIndvcmQiKSkKYW50aV9qb2luKHRlc3QsIG5vcGUsIGJ5PWMoImZhY3Rvcih2YWx1ZSkiID0gIndvcmQiKSkKYGBgCidib3JpbmcnLCAnZmFsY29uJywgJ3Rlc2xhJywgJ3JvY2tldCcsICdAc3BhY2V4JywgJ2NvbXBhbnknLCAnbG92ZScsICdsYXVuY2gnLCAnYWknLCAnY2FycycuICdmbGFtZXRocm93ZXInIGFuZCAnbWFycycgYXJlIGEgYml0IGZ1cnRoZXIgZG93bi4KCmBgYHtyfQpucmMgPC0gc2VudGltZW50cyAlPiUKICBmaWx0ZXIobGV4aWNvbiA9PSAibnJjIikgJT4lCiAgZHBseXI6OnNlbGVjdCh3b3JkLCBzZW50aW1lbnQpCgpucmMKI2FiYWN1cyBhc3NvY2lhdGVkIHdpdGggdHJ1c3QuLi53ZWlyZAoKYnlfc291cmNlX3NlbnRpbWVudCA8LSB0cnVtcF93b3JkcyAlPiUKICBpbm5lcl9qb2luKG5yYywgYnkgPWMoImZhY3Rvcih2YWx1ZSkiID0gIndvcmQiKSkgJT4lCiAgY291bnQoc2VudGltZW50KSAlPiUgCiAgYXJyYW5nZShkZXNjKG5uKSkKICAKCmhlYWQoYnlfc291cmNlX3NlbnRpbWVudCkKYGBgCgpgYGB7cn0KZGFpbHlfdHdlZXRzX2RmJHRleHQgPC0gZ3N1YihwYXR0ZXJuID0gImh0dHBbc10/Oi8vW1s6YWxudW06XS5cXC9dKyIsIHJlcGxhY2VtZW50ID0gIiIsIGRhaWx5X3R3ZWV0c19kZiR0ZXh0KQoKZGFpbHlfdHdlZXRzX2RmJHRleHQgPC0gZ3N1YihwYXR0ZXJuID0gIlsgXSskIiwgcmVwbGFjZW1lbnQgPSAiIiwgZGFpbHlfdHdlZXRzX2RmJHRleHQpCgoKc2F2ZSA8LSBzdHJfZXh0cmFjdF9hbGwoZGFpbHlfdHdlZXRzX2RmJHRleHQsICJbI3xAfFs6YWxudW06XV0rKFteXFxzXVtbOmFsbnVtOl1dKyk/Iiwgc2ltcGxpZnkgPSBUUlVFKQojZ2F0aGVyLCBnZXQgcmlkIG9mIGVtcHR5IHN0cmluZ3MKdGVzdCA8LSBnYXRoZXIoYXMuZGF0YS5mcmFtZShzYXZlKSkgJT4lIGZpbHRlcih2YWx1ZSAhPSAiIikKCnRlc3QkdmFsdWUgPC0gdG9sb3dlcih0ZXN0JHZhbHVlKQoKdGVzdCA8LSB0ZXN0ICU+JSBzZWxlY3QodmFsdWUpICU+JSBjb3VudChmYWN0b3IodmFsdWUpKSAlPiUgYXJyYW5nZShkZXNjKG4pKQoKZGFpbHlfd29yZHMgPC0gYW50aV9qb2luKHRlc3QsIG5vcGUsIGJ5PWMoImZhY3Rvcih2YWx1ZSkiID0gIndvcmQiKSkKZGFpbHlfd29yZHMgPC0gYW50aV9qb2luKHRlc3QsIG5vcGUsIGJ5PWMoImZhY3Rvcih2YWx1ZSkiID0gIndvcmQiKSkKYGBgCgoKCgpgYGB7cn0KdHJ1bXBfdHdlZXRzX2RmJHRleHQgPC0gZ3N1YihwYXR0ZXJuID0gImh0dHBbc10/Oi8vW1s6YWxudW06XS5cXC9dKyIsIHJlcGxhY2VtZW50ID0gIiIsIHRydW1wX3R3ZWV0c19kZiR0ZXh0KQoKdHJ1bXBfdHdlZXRzX2RmJHRleHQgPC0gZ3N1YihwYXR0ZXJuID0gIlsgXSskIiwgcmVwbGFjZW1lbnQgPSAiIiwgdHJ1bXBfdHdlZXRzX2RmJHRleHQpCgoKc2F2ZSA8LSBzdHJfZXh0cmFjdF9hbGwodHJ1bXBfdHdlZXRzX2RmJHRleHQsICJbI3xAfFs6YWxudW06XV0rKFteXFxzXVtbOmFsbnVtOl1dKyk/Iiwgc2ltcGxpZnkgPSBUUlVFKQojZ2F0aGVyLCBnZXQgcmlkIG9mIGVtcHR5IHN0cmluZ3MKdGVzdCA8LSBnYXRoZXIoYXMuZGF0YS5mcmFtZShzYXZlKSwgdmFsdWUgPSAid29yZCIpICU+JSBmaWx0ZXIod29yZCAhPSAiIikKCnRlc3Qkd29yZCA8LSB0b2xvd2VyKHRlc3Qkd29yZCkKCnRlc3QgPC0gdGVzdCAlPiUgc2VsZWN0KHdvcmQpICU+JSBjb3VudCh3b3JkKSAlPiUgYXJyYW5nZShkZXNjKG4pKQoKdHJ1bXBfd29yZHMgPC0gYW50aV9qb2luKG5vcGUsIG5vcGUsIGJ5PSAid29yZCIpCgpgYGAKCkl0IGxvb2tzIGxpa2Ugd2UgbmVlZCBzb21lIG1vcmUgZGF0YSBjbGVhbmluZy4gRmlyc3QsIGxldCdzIGdldCByaWQgb2YgZXZlcnl0aGluZyB3aXRoIG51bWJlcnMuCgpgYGB7cn0KdHJ1bXBfd29yZHMgJT4lIHNlbGVjdCh3b3JkKSAlPiUgc3RyX3JlbW92ZSgiWzAtOV0rIiwgc2ltcGxpZnkpCgojcmVtb3ZlcyBudW1iZXJzCnN0cl9yZW1vdmVfYWxsKHRydW1wX3dvcmRzJHdvcmQsICJbMC05XSoiKQojc3RpbGwgaGF2ZSBwdW5jdHVhdGlvbiBiZWZvcmUgbnVtYmVycwpzdHJfcmVtb3ZlX2FsbCh0cnVtcF93b3JkcyR3b3JkLCAiWzAtOV0uKiIpCgoKYGBgCkl0J3MgbG9va2luZyBiZXR0ZXIuIFdlIGhhdmUgYSBzaW5nbGUgaGFzaHRhZy4gIidzIiBlbmRpbmdzIHNob3VsZCBiZSByZW1vdmVkIC0gY291bGQgbWF0Y2ggb3RoZXIgd29yZHMsIG9yIGlmIGEgY29udHJhY3Rpb24gd2lsbCBiZSByZW1vdmVkIHZpYSBzdG9wd29yZHMgbGlzdC4gVGhlcmUgaXMgYWxzbyBhICd1LnMnIHdoZXJlIHdlIGNhbiBnZXQgcmlkIG9mIHRoZSBwZXJpb2QuIElmIGFueW9uZSBjYW4gZmluZCBvdXQgaG93IHRvIHJlbW92ZSB0aGUgYXBvc3Ryb3BoZSBhbmQgbm90IHRoZSBwZXJpb2QsIGxldCBtZSBrbm93LgoKYGBge3J9CnN0cl9yZW1vdmVfYWxsKHRydW1wX3dvcmRzJHdvcmQsICIjJCIpCgoKZ3N1YigiW1s6cHVuY3Q6XV1zJCIsICIiLCB0cnVtcF93b3JkcyR3b3JkKQojbGV0J3MgY2hlY2sgdG8gc2VlIHdoYXQgdGhpcyB3aWxsIHJlbW92ZS4KZ3JlcCgiW1s6cHVuY3Q6XV1zJCIsIHRydW1wX3dvcmRzJHdvcmQsIHZhbHVlPVRSVUUpCiN0aGUgb25seSB0aGluZyB0aGF0IGlzbid0IGFuICdzIGlzIHUucywgd2UgZG9uJ3Qgd2FudCB0aGlzIHJlbW92ZWQgYW5kIHRydW5jYXRlZCB0byAndScsIGJ1dCB3ZSBhbHNvIGRvbid0IHdhbnQgdG8ganVzdCByZW1vdmUgdGhlIHBlcmlvZCBmaXJzdCBiZWNhdXNlIHdlIHdhbnQgdG8gcmV0YWluIHRoYXQgaXQgbWVhbnMgdW5pdGVkIHN0YXRlcyBhbmQgbm90IHVzLiBTbywgSSBhY3R1YWxseSBjb3VsZG4ndCBmaW5kIGEgcmVnZXggcHVuY3R1YXRpb24gc29sdXRpb24gdG8gdGhpcy4gQk9OVVMgcG9pbnRzIGlmIHlvdSBkby4gSW5zdGVhZCwgd2UgYXJlIGdvaW5nIHRvIFJFUExBQ0UgInUucyIgd2l0aCAidXNhIgoKdHJ1bXBfd29yZHMkd29yZCA8LSBzdHJfcmVwbGFjZSh0cnVtcF93b3JkcyR3b3JkLCAidS5zIiwgInVzYSIpCgojY2hlY2sKZ3JlcCgiW1s6cHVuY3Q6XV1zJCIsIHRydW1wX3dvcmRzJHdvcmQsIHZhbHVlPVRSVUUpCgp0cnVtcF93b3JkcyR3b3JkIDwtIHN0cl9yZW1vdmVfYWxsKHRydW1wX3dvcmRzJHdvcmQsICJbWzpwdW5jdDpdXXMkIikKdHJ1bXBfd29yZHMkd29yZCA8LSBzdHJfcmVtb3ZlX2FsbCh0cnVtcF93b3JkcyR3b3JkLCAiIyQiKQoKI2NoZWNrCmdyZXAoIltbOnB1bmN0Ol1dcyQiLCB0cnVtcF93b3JkcyR3b3JkLCB2YWx1ZT1UUlVFKQoKCiNvbmNlIHdlIGtub3cgd2UndmUgZ290IGl0IHJpZ2h0IHdlIGNhbiBmaWx0ZXIgdGhlIGRhdGEgZnJhbWUKdHJ1bXBfd29yZHMgPC0gdHJ1bXBfd29yZHMgJT4lIG11dGF0ZSh3b3JkID0gc3RyX3JlbW92ZV9hbGwodHJ1bXBfd29yZHMkd29yZCwgIlswLTldLioiKSkgJT4lIGZpbHRlcih3b3JkICE9ICIiKQpgYGAKCldlIG5lZWQgdG8gYWRkIHRvIG91ciAnbm9wZScgbGlzdCB3b3JkcyB1bnJlbGF0ZWQgdG8gY29udGVudCBidXQgaHRtbCBqYXJnb24sIG9yIHRoaW5ncyBsaWtlICduYScgYW5kICdmYWxzZScuCgpgYGB7cn0KYWRkX3N0b3AgPC0gZGF0YS5mcmFtZSh3b3JkID0gYygibmEiLCAiZmFsc2UiLCAiaHJlZiIsICJyZWwiLCAibm9mb2xsb3ciLCAidHJ1ZSIsICJhbXAiLCAidHdpdHRlciIsICJpcGhvbmUiKSwgCiAgICAgICAgICAgICAgICAgICAgICAgbGV4aWNvbiA9ICJjdXN0b20iKQojSSBqdXN0IHNob3VsZCBwb2ludCBvdXQgdGhhdCB0cnVlIG9yIGZhbHNlICdjb3VsZCcgYmUgYSB3b3JkIHJhdGhlciB0aGFuIGEgbG9naWNhbCwgSSBjb3VsZCBmaWx0ZXIgb3V0IEZBTFNFIGFuZCBUUlVFIGJlZm9yZSBjaGFuZ2luZyBldmVyeXRoaW5nIHRvIGxvd2VyY2FzZSBhbmQgdGhpcyB3b3VsZCByZWR1Y2UgdGhlIGxpa2VseWhvb2Qgb2YgbWlzc2luZyAnd29yZHMnCm5vcGUgPC0gYmluZF9yb3dzKG5vcGUsIGFkZF9zdG9wKQpgYGAKCmBgYHtyfQp0cnVtcF93b3JkcyA8LSBhbnRpX2pvaW4odGVzdCwgbm9wZSwgYnk9ICJ3b3JkIikKYGBgCgoKCkJvdGggTnllIGFuZCBNdXNrIHBvc2l0aXZlLiBBcnRpY2xlIGRvZXMgcC12YWx1ZSBhbmQgc3VjaC4gUHJvYmFibHkganVzdCBzaG91bGQgZG8gbW9yZSByZWdleC4KaHR0cDovL3ZhcmlhbmNlZXhwbGFpbmVkLm9yZy9yL3RydW1wLXR3ZWV0cy8KCmZhdm91cml0ZWQgcHJvYmFibHkgYmV0dGVyIHRoYW4gcmV0d2VldGVkIGlmIHVzaW5nIG9uZQoKCkNoYWxsZW5nZSBxdWVzdGlvbjogUGljayBvbmUgb2YgdGhlIG90aGVyIHR3ZWV0IGRhdGEgc2V0cy4gQ2xlYW4gaXQuIFJlbW92ZSBhbGwgb2YgdGhlIHN0b3Agd29yZHMuIE1ha2UgYSBiYXIgcGxvdCBvZiB0aGUgdG9wIDIwIHdvcmRzIGFuZCB0aGVpciBjb3VudHMuCgoKCmBgYHtyfQpsaWJyYXJ5KCJ3b3JkY2xvdWQiKQpsaWJyYXJ5KCJ2aXJpZGlzIikKCnRydW1wX3dvcmRzICU+JQogICAgdG9wX24oNTApICU+JQogICAgd2l0aCh3b3JkY2xvdWQod29yZCwgbiwgb3JkZXJlZC5jb2xvcnMgPSBUUlVFLCBjb2xvcnMgPSB2aXJpZGlzKDYxKSwgdXNlLnIubGF5b3V0ID0gVFJVRSkpCmBgYAoKCkkgbG9va2VkIGZvciBhIGRhdGFzZXQgZm9yIGRhdGEgY2xlYW5pbmcgYW5kIGZvdW5kIGl0IGluIGEgYmxvZyB0aXRsZWQgIkJpb2xvZ2lzdHM6IHRoaXMgaXMgd2h5IGJpb2luZm9ybWF0aWNpYW5zIGhhdGUgeW91Li4uIi4gVGhlIG1haW4gYW5kIGNvbW1vbiBpc3N1ZSB3aXRoIHRoaXMgZGF0YXNldCBpcyB0aGF0IHdoZW4gZGF0YSBlbnRyeSB3YXMgZG9uZSB0aGVyZSB3YXMgbm8gc3RydWN0dXJlZCB2b2NhYnVsYXJ5IC0gbWVhbmluZyB0aGF0IHBlb3BsZSBjb3VsZCB0eXBlIGluIHdoYXRldmVyIHRoZXkgd2FudGVkIGluc3RlYWQgb2YgdXNpbmcgZHJvcGRvd24gbWVudXMgd2l0aCBsaW1pdGVkIG9wdGlvbnMsIG9yIGdpdmluZyBhbiBlcnJvciBpZiBzb21ldGhpbmcgaXMgZm9ybWF0dGVkIGluY29ycmVjdGx5LCBvciBzdGlwdWxhdGluZyBzb21lIHJ1bGVzIChpZS4gbXVzdCBiZSBhbGwgbG93ZXJjYXNlLCB1cHBlcmNhc2UsIG5vIG51bWJlcnMsIHNwYWNpbmcsIGV0Yy4pLiBJIG11c3QgYWRtaXQgSSBoYXZlIGJlZW4gZ3VpbHR5IG9mIG1lc3Npbmcgd2l0aCBwZW9wbGUgd2hvIGhhdmUgbWFkZSBkYXRhYmFzZXMgd2l0aG91dCBydWxlcy4gRm9yIGV4YW1wbGUsIGluIGdpdmluZyB0aGUgZW1lcmdlbmN5IGNvbnRhY3QgdGhlcmUgd2FzIGEgbGluZSB0byBpbnB1dCAnUmVsYXRpb25zaGlwJywgd2hpY2ggY291bGQgZWFzaWx5IGhhdmUgYmVlbiBhIGRyb3Bkb3duIG1lbnUgJ3BhcmVudCwgcGFydG5lciwgZnJpZW5kLCBvdGhlcicsIGJ1dCBpbnN0ZWFkIEkgd2FzIGFsbG93ZWQgdG8gd3JpdGUgaW4gYSBmcmVlIHRleHQgbGluZSAnbGlmZWxvbmcga2luZHJlZCBzcGlyaXQsIHNvdWxtYXRlIGFuZCBkb2dneS1kYWRkeScuIEkgZG9uJ3QgdGhpbmsgYW55b25lIGhlcmUgd2FzIHRyeWluZyB0byBiZSBhIG51aXNhbmNlLCB0aGlzIGlzIGp1c3QgYSBjb25zZXF1ZW5jZSBvZiBwb29yIGRhdGEgY29sbGVjdGlvbi4gVGhlcmUgaXMgYSBSRUFETUUgZmlsZSB0byBnbyB3aXRoIHRoaXMgc3ByZWFkc2hlZXQgaWYgeW91IGhhdmUgcXVlc3Rpb25zIGFib3V0IHRoZSBkYXRhIGZpZWxkcy4gIAoKaHR0cDovL3d3dy5vcGluaW9taWNzLm9yZy9iaW9sb2dpc3RzLXRoaXMtaXMtd2h5LWJpb2luZm9ybWF0aWNpYW5zLWhhdGUteW91LyAgICAgCmh0dHBzOi8vZmlnc2hhcmUuY29tL2FydGljbGVzL1dlbGxjb21lX1RydXN0X0FQQ19zcGVuZF8yMDEyXzEzX2RhdGFfZmlsZS85NjMwNTQKCldoYXQgSSB3YW50IHRvIGtub3cgaXM6IAoxLiBMaXN0IDUgcHJvYmxlbXMgd2l0aCB0aGlzIGRhdGEgc2V0LgoxLiBXaGljaCBwdWJsaXNoZXIgaXMgdGhlIG1vc3QgZXhwZW5zaXZlIHRvIHB1Ymxpc2ggd2l0aD8KMS4gV2hpY2ggam91cm5hbCBpcyB0aGUgbW9zdCBleHBlbnNpdmUgdG8gcHVibGlzaCB3aXRoPyBJcyB0aGlzIGJ5IHRoZSBzYW1lIHB1Ymxpc2hlcj8gICAgICAgICAgICAgICAgICAKMS4gQ29udmVydCBzdGVybGluZyB0byBDQUQuIFdoYXQgaXMgdGhlIG1lZGlhbiBjb3N0IG9mIHB1Ymxpc2hpbmcgd2l0aCBFbHNldmllciBpbiBDQUQ/CgpUaGUgYmxvZ2dlcidzIG9waW5pb24gb2YgY2xlYW5pbmcgdGhpcyBkYXRhc2V0OgoKJ0kgbm93IGhhdmUgbm8gaGFpciBsZWZ0OyBJ4oCZdmUgdG9ybiBpdCBhbGwgb3V0LiAgTXkgdGVldGggYXJlIGp1c3Qgc3R1bXBzIGZyb20gZXhjZXNzaXZlIGduYXNoaW5nLiAgTXkgZmFpdGggaW4gaHVtYW5pdHkgaGFzIGJlZW4gZGVzdHJveWVkIScKCkRvbid0IGdldCB0byB0aGlzIHBvaW50LiBUaGUgZGF0YXNldCBkb2Vzbid0IG5lZWQgdG8gYmUgcGVyZmVjdC4gSnVzdCBkbyB3aGF0IHlvdSBnb3R0YSBkbyB0byBhbnN3ZXIgdGhlc2UgcXVlc3Rpb25zLiAgCgpOb3RlIHRvIHNlbGY6IFRoaXMgbWF5IGJlIHRvbyB0b3VnaCAtIHNlZSBob3cgbG9uZyBpdCB0YWtlcyB0byBkby4KCkFwcHJveGltYXRlIHRpbWU6IDIgaG91cnMgcGVyIGxlc3NvbgoKIF9FYWNoIGxlc3NvbiB3aWxsIGhhdmU6XwogCiAtIENvbXByZWhlbnNpb24gcXVlc3Rpb25zIGFzIHdlIGdvIGFsb25nIG9uIFNvY3JhdGl2ZS4KIC0gSG93IHRvIHJlYWQgaGVscCBwYWdlcyBvbmxpbmUuCiAtIEdpdmUgdGhlIGNsYXNzIGEgZnVuY3Rpb24gbm90IHByZXZpb3VzbHkgdXNlZCBkdXJpbmcgdGhlIGxlc3NvbiBhbmQgaGF2ZSB0aGVtIGZpZ3VyZSBvdXQgd2hhdCBpdCBkb2VzIGFuZCBob3cgdG8gdXNlIGl0LgogLSBFYWNoIGxlc3NvbiB3aWxsIHN0YXJ0IGZyb20gYW4gZXhjZWwgc3ByZWFkc2hlZXQgd2l0aCBpbXBlcmZlY3QgZGF0YS4KCioqKgoKX1IgbWFya2Rvd24gYW5kIGtuaXRyXwoKLSByIG1hcmtkb3duIHN5bnRheAogICAgKyAgbWFraW5nIHRoaW5ncyBwcmV0dHk6IGFkZGluZyB0YWJsZSBvZiBjb250ZW50cywgaW1hZ2VzLCBoeXBlcmxpbmtzLCB1cmxzCi0ga25pdHIgY29kZSBjaHVuayBvcHRpb25zCiAgICArIHN1cHByZXNzaW5nIHBrZyBsb2FkIHdhcm5pbmdzLCBldmFsID0gVC9GLCByZS1ydW5uaW5nIHNvbWUgY2h1bmtzIHdoaWxlIGtlZXBpbmcgb3RoZXJzIGluIG1lbW9yeQogICAgKyB0YWJsZXMgaW4ga25pdHIKLSByZW5kZXJpbmcgdG8gcGRmLCBodG1sLCB3b3JkIGRvY3VtZW50cyAoYW55IGludGVyZXN0IGluIHNsaWRlcz8pCi0gc2hhcmluZyBvbiBScHVicwoKX19DaGFsbGVuZ2U6X18gICAgICAKClRha2UgdGhlIG9yaWdpbmFsIGdhcG1pbmRlciBkYXRhc2V0IGFuZCBjb3ZlcnQgaXQgdG8gdGhlICdjbGVhbicgZGF0YXNldCBmb3VuZCBpbiB0aGUgZ2FwbWluZGVyIHBhY2thZ2UgLyBmaW5kIHNvbWUgaG9ycmlibGUgZGF0YXNldCB0byBjbGVhbi4gUHJlc2VudCBpbiBhIGtuaXRyIHRhYmxlLCBleHBsYWluaW5nIHNvbWUgb2YgeW91ciBkYXRhIGNsZWFuaW5nIGNoYWxsZW5nZXMgaW4gcm1hcmtkb3duLiBLbml0IHRoZSBkb2N1bWVudCB0byBhIHBkZi4KICAgCl9fUmVzb3VyY2VzOl9fICAgICAKaHR0cDovL3N0YXQ1NDUuY29tL2Jsb2NrMDIyX3JlZ3VsYXItZXhwcmVzc2lvbi5odG1sCmh0dHA6Ly9zdGF0NTQ1LmNvbS9ibG9jazAyN19yZWd1bGFyLWV4cHJlc3Npb25zLmh0bWwKaHR0cDovL3N0YXQ1NDUuY29tL2Jsb2NrMDI4X2NoYXJhY3Rlci1kYXRhLmh0bWwgICAgIApodHRwOi8vcjRkcy5oYWQuY28ubnovc3RyaW5ncy5odG1sCmh0dHA6Ly93d3cuZ2FzdG9uc2FuY2hlei5jb20vSGFuZGxpbmdfYW5kX1Byb2Nlc3NpbmdfU3RyaW5nc19pbl9SLnBkZiAgICAgCmh0dHA6Ly92YXJpYW5jZWV4cGxhaW5lZC5vcmcvci90cnVtcC10d2VldHMvICAgICAKCiMjUG9zdC1MZXNzb24gQXNzZXNzbWVudAoqKioKX1F1ZXN0aW9uc18KCi0gU3BlZWQ6IFRvbyBzbG93LCB0b28gZmFzdCwganVzdCByaWdodAotIENvbnRlbnQ6IFRvbyBlYXN5LCB0b28gaGFyZCwganVzdCByaWdodAotIEZyb20gdGhlIGRlc2NyaXB0aW9uIG9mIHRoZSBsZXNzb24sIHRoZSBjb250ZW50IHdhcyB3aGF0IEkgZXhwZWN0ZWQgdG8gbGVhcm4uIFQvRgotIFdoYXQgd2FzIHRoZSBtb3N0IHVzZWZ1bCB0aGluZyB5b3UgbGVhcm5lZD8KLSBXaGF0IHdhcyB0aGUgbGVhc3QgdXNlZnVsIHRoaW5nPwotIENvbW1lbnRzL3N1Z2dlc3Rpb25zIGZvciBpbXByb3ZlbWVudC4KCgojI05vdGVzCioqKgotIFBvc3NpYmx5IHNwbGl0IGxlc3NvbiA1IGludG8gMiBsZXNzb25zOiBCYXNpYyBTdGF0aXN0aWNzIGFuZCBMaW5lYXIgcmVncmVzc2lvbgotIE1heWJlIHN3aXRjaCBSZWdleCBhbmQgUGxvdHRpbmcgbGVzc29uIG9yZGVy